package controllers;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

import models.Ingredient;
import models.Recipe;
import models.RecipeComment;
import play.Logger;
import play.cache.Cache;
import play.cache.CacheFor;
import play.data.validation.Required;
import play.data.validation.Validation;
import play.modules.gae.GAE;
import play.mvc.Controller;
import play.mvc.Http.Response;
import play.mvc.With;
import siena.Query;
import cn.bran.play.JapidController;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.users.User;

@With(MenuInjector.class)
public class Application extends JapidController {
    private static DateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
    private static int PAGE_SIZE = 10;
    private static Query<Recipe> recipesPagingQ;
    static {
        Application.httpDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
    }

    public static void addRecipe(@Required String name, @Required String description, @Required String author) {
        if (Validation.hasErrors()) {
            Controller.params.flash();
            Validation.keep();
            addRecipeForm();
        } else {
            Recipe recipe = new Recipe();
            recipe.name = name;
            recipe.description = description;

            recipe.author = author;
            recipe.display = false;
            recipe.insert();

            Controller.flash.success("Спасибо большое, %s. Ваш рецепт будет опубликован вскоре после модерации.",
                            author);
            addRecipeForm();
        }
    }

    public static void addRecipeComment(@Required String comment, Long id) {
        Recipe recipe = new Recipe();
        recipe.id = id;
        try {
            recipe.get();
        } catch (RuntimeException e) {
            notFound();
        }

        User user = GAE.getUser();
        if (user == null) {
            login("Application.viewRecipeByCode", recipe.code);
        }
        if (Validation.hasErrors()) {
            Logger.warn("Validation violation");
            Controller.params.flash();
            Validation.keep();
        } else {
            RecipeComment recipeComment = new RecipeComment(comment, user.getEmail());
            recipeComment.recipe = recipe;
            recipeComment.save();
        }

        viewRecipeByCode(recipe.code);
    }

    public static void addRecipeForm() {
        renderJapid();
    }

    private static BufferedInputStream createInputStream(com.google.appengine.api.datastore.Blob blob) {
        return new BufferedInputStream(new ByteArrayInputStream(blob.getBytes()));
    }

    private static Blob getCachedThumb(String code) {
        String key = "recipeThumb:" + code;
        Blob thumb = (Blob) Cache.get(key);
        if (thumb == null) {
            Recipe recipe = Recipe.all().filter("code", code).get();
            recipe.get();
            thumb = recipe.thumb;
            Cache.set(key, thumb, "4d");
        }
        return thumb;
    }

    @CacheFor("29d")
    public static void index() {
        renderJapid();
    }

    // @Before
    public static void loadRecipesPagingQ() {
        Application.recipesPagingQ = (Query<Recipe>) Cache.get("recipesPagingQ");
        if (Application.recipesPagingQ == null) {
            Application.recipesPagingQ = Recipe.all().stateful().paginate(Application.PAGE_SIZE);
        }
    }

    public static void login(String action, String recipeCode) {
        if ((recipeCode != null) && action.equals("Application.viewRecipeByCode")) {
            Map<String, Object> params = new HashMap<String, Object>();
            params.put("code", recipeCode);
            GAE.login(action, params);
        } else if (!action.equals("Application.viewRecipeByCode")) {
            GAE.login(action);
        } else {
            GAE.login("Application.index");
        }
    }

    public static void logout() {
        Controller.session.clear();
        GAE.logout("Application.index");
    }

    public static void recipeImage(String code) {
        Recipe recipe = Recipe.all().filter("code", code).get();
        if (recipe.image == null) {
            Logger.info("Recipe image is not found for recipeCode[" + code + "]");
            notFound("Image");
        } else {
            setCacheFarFutureHeaders(Controller.response);
            renderBinary(createInputStream(recipe.image));
        }
    }

    public static void recipeImage2(String code) {
        Recipe recipe = Recipe.all().filter("code", code).get();
        if (recipe.image2 == null) {
            Logger.info("Recipe image2 is not found for recipeCode[" + code + "]");
            notFound("Image2");
        } else {
            setCacheFarFutureHeaders(Controller.response);
            renderBinary(createInputStream(recipe.image2));
        }
    }

    @CacheFor("1h")
    public static void recipes() {
        // if (next) {
        // Application.recipesPagingQ.nextPage();
        // } else if (prev) {
        // Application.recipesPagingQ.previousPage();
        // }
        List<Recipe> recipes = Recipe.all().filter("display", true).fetch();
        renderJapid(recipes);
    }

    public static void recipeThumb(String code) {
        long time = System.currentTimeMillis();
        Blob thumb = getCachedThumb(code);
        if (thumb == null) {
            Logger.info("Recipe thumb is not found for recipeCode[" + code + "]");
            notFound("Thumb");
        } else {
            setCacheFarFutureHeaders(Controller.response);
            Logger.debug("recipeThumb() time: " + (System.currentTimeMillis() - time) + " ms");
            renderBinary(createInputStream(thumb));
        }
    }

    public static void recipeThumb2(String code) {
        Recipe recipe = Recipe.all().filter("code", code).get();
        recipe.get();
        if (recipe.thumb2 == null) {
            Logger.info("Recipe thumb2 is not found for recipeCode[" + code + "]");
            notFound("Thumb2");
        } else {
            setCacheFarFutureHeaders(Controller.response);
            renderBinary(createInputStream(recipe.thumb2));
        }
    }

    // @After
    // public static void saveRecipesPagingQ() {
    // Cache.set("recipesPagingQ", Application.recipesPagingQ);
    // }

    public static void redirectToAddRecipeForm() {
        redirect("/addRecipeForm", true);
    }

    private static void setCacheExpireDate(Response response, int seconds) {
        Calendar cal = new GregorianCalendar();
        cal.roll(Calendar.SECOND, seconds);
        response.setHeader("Cache-Control", "PUBLIC, max-age=" + seconds + ", must-revalidate");
        response.setHeader("Expires", Application.httpDateFormat.format(cal.getTime()));
    }

    private static void setCacheFarFutureHeaders(Response response) {
        setCacheExpireDate(Controller.response, 345600);
    }

    public static void viewRecipeByCode(String code) {
        Recipe recipe = Recipe.all().filter("code", code).get();
        if (recipe == null) {
            notFound();
        } else {
            Query<Ingredient> ingrs = recipe.ingredients;
            for (Ingredient ingr : ingrs.fetch()) {
                ingr.product.get();
                ingr.recipe.get();
            }
            renderJapid(recipe);
        }
    }
}